package com.jiang.demo.config.knife4j;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.ttl.threadpool.agent.internal.javassist.*;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.AnnotationsAttribute;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.ConstPool;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.annotation.Annotation;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.annotation.BooleanMemberValue;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.annotation.IntegerMemberValue;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.annotation.StringMemberValue;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.jiang.demo.aspect.ApiEx;
import com.jiang.demo.aspect.ApiIn;
import com.jiang.demo.common.util.ReflectUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * ---------------------------------------------------------------------------------------------------------------
 * - 2024-06-01 15:24 - jiangshaoneng -
 * ---------------------------------------------------------------------------------------------------------------
 * -      ██╗██╗ █████╗ ███╗   ██╗ ██████╗ ███████╗██╗  ██╗ █████╗  ██████╗ ███╗   ██╗███████╗███╗   ██╗ ██████╗
 * -      ██║██║██╔══██╗████╗  ██║██╔════╝ ██╔════╝██║  ██║██╔══██╗██╔═══██╗████╗  ██║██╔════╝████╗  ██║██╔════╝
 * -      ██║██║███████║██╔██╗ ██║██║  ███╗███████╗███████║███████║██║   ██║██╔██╗ ██║█████╗  ██╔██╗ ██║██║  ███╗
 * - ██   ██║██║██╔══██║██║╚██╗██║██║   ██║╚════██║██╔══██║██╔══██║██║   ██║██║╚██╗██║██╔══╝  ██║╚██╗██║██║   ██║
 * - ╚█████╔╝██║██║  ██║██║ ╚████║╚██████╔╝███████║██║  ██║██║  ██║╚██████╔╝██║ ╚████║███████╗██║ ╚████║╚██████╔╝
 * -  ╚════╝ ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═══╝ ╚═════╝
 * ---------------------------------------------------------------------------------------------------------------
 * 读取自定义忽略的dto属性并动态生成model
 * ---------------------------------------------------------------------------------------------------------------
 */
@Slf4j
@Component
public class Knife4jParameterBuilderPlugin implements ParameterBuilderPlugin {

    @Autowired
    private TypeResolver typeResolver;

    @Override
    public void apply(ParameterContext context) {
        ResolvedMethodParameter methodParameter = context.resolvedMethodParameter();
        Optional<ApiIn> include = methodParameter.findAnnotation(ApiIn.class);
        Optional<ApiEx> exclude = methodParameter.findAnnotation(ApiEx.class);
        if (include.isPresent() || exclude.isPresent()) {
            Class<?> originClass = context.resolvedMethodParameter().getParameterType().getErasedType();
            String name = originClass.getSimpleName() + "_" + IdUtil.objectId();  //model 名称
            String[] properties;
            int annoType = 0;
            if (include.isPresent()){
                properties = include.get().value();
            }else {
                properties = exclude.get().value();
                annoType = 1;
            }
            try {
                //像documentContext的Models中添加我们新生成的Class
                //ResolvedType resolve = typeResolver.resolve(createRefModelIgp(Arrays.asList(properties), name, originClass, annoType));
                ResolvedType resolve = typeResolver.resolve(createRefModelIgp(Arrays.asList(properties), name, originClass, annoType));
                context.getDocumentationContext().getAdditionalModels().add(resolve);
                //修改model参数的ModelRef为我们动态生成的class
                context.parameterBuilder().parameterType("body").modelRef(new ModelRef(name)).name(name).build();
            } catch (NotFoundException ignore) {}
        }
    }


    /**
     * 根据properties中的值动态生成含有Swagger注解的javaBeen
     */
    private Class<?> createRefModelIgp(List<String> properties, String name, Class<?> origin, Integer annoType) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(origin.getPackage().getName()+"."+name);
        try {
            Field[] fields = ReflectUtil.getAllDeclaredFields(origin);
            List<Field> fieldList = Arrays.asList(fields);
            List<Field> dealFields = fieldList.stream().filter(s-> (annoType == 0) == properties.contains(s.getName())).collect(Collectors.toList());
            createCtFields(dealFields, ctClass);
            return ctClass.toClass();

        } catch (Exception e) {
            log.error("createRefModelIgp:", e);
        }
        return null;
    }

    private void createCtFields(List<Field> dealFields, CtClass ctClass) throws CannotCompileException, NotFoundException, NoSuchFieldException, IllegalAccessException {
        for (Field field : dealFields) {
            CtField ctField;
            if("java.util.List".equals(field.getType().getName())){
                ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);
                // 设置泛型签名
                Class<Field> fieldClass = Field.class;
                Field privateField = fieldClass.getDeclaredField("signature");
                privateField.setAccessible(true);
                String genericSignature = (String)privateField.get(field);
                ctField.setGenericSignature(genericSignature);
            }else {
                ClassPool pool = ClassPool.getDefault();
                pool.insertClassPath(new ClassClassPath(field.getType()));
                ctField = new CtField(pool.get(field.getType().getName()), field.getName(), ctClass);
            }
            ctField.setModifiers(Modifier.PUBLIC);

            ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
            String apiModelPropertyValue = Optional.ofNullable(annotation).map(ApiModelProperty::value).orElse("");
            String apiModelPropertyNotes = Optional.ofNullable(annotation).map(ApiModelProperty::notes).orElse("");
            boolean apiModelPropertyRequired = Optional.ofNullable(annotation).map(ApiModelProperty::required).orElse(false);
            int apiModelPropertyPosition = Optional.ofNullable(annotation).map(ApiModelProperty::position).orElse(0);
            if(apiModelPropertyPosition == 0){
                apiModelPropertyPosition = ArrayUtils.indexOf(dealFields.toArray(dealFields.toArray(new Field[0])), field);
            }
            String value = ReflectUtil.joinText(apiModelPropertyNotes, apiModelPropertyValue);
            if(!StringUtils.isAllBlank(value)){
                apiModelPropertyValue = value;
            }

            if (StrUtil.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
                ConstPool constPool = ctClass.getClassFile().getConstPool();
                AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue, constPool));
                ann.addMemberValue("notes", new StringMemberValue(apiModelPropertyNotes, constPool));
                ann.addMemberValue("required", new BooleanMemberValue(apiModelPropertyRequired, constPool));
                ann.addMemberValue("position", new IntegerMemberValue(constPool, apiModelPropertyPosition));

                attr.addAnnotation(ann);
                ctField.getFieldInfo().addAttribute(attr);
            }
            ctClass.addField(ctField);
        }
    }


    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}